Skip to content

feat(daemon): local daemon owns the engine, DaemonClient default (M2.5)#218

Merged
vreshch merged 1 commit into
masterfrom
feature/m2_5-daemon
Jul 6, 2026
Merged

feat(daemon): local daemon owns the engine, DaemonClient default (M2.5)#218
vreshch merged 1 commit into
masterfrom
feature/m2_5-daemon

Conversation

@vreshch

@vreshch vreshch commented Jul 6, 2026

Copy link
Copy Markdown
Member

What

M2.5: a local daemon becomes the default owner of the memory engine. One long-lived process serialises all vault mutations (no concurrent git index.lock collisions) and is the future host of the local MCP surface.

  • src/daemon/ - loopback-only node:http server: GET /api/health ({ok, version, pid, uptime, served}) + POST /api/memory/<verb> for the 6 frozen verbs, dispatching to ONE shared in-process MemoryClient (the same createDirectClient the CLI uses). Engine is rebuilt only when vaults.json changes on disk, so vault add is picked up without a restart.
  • src/daemon-entry.ts - the detached entrypoint: pidfile + port file under the config dir, graceful SIGTERM/SIGINT shutdown.
  • src/lib/daemon-client.ts - DaemonClient implements the MemoryClient interface over HTTP; ensureDaemon() prefers a live daemon, autostarts one (detached spawn + health poll) when absent, and returns null on failure so verbs fall back to the in-process DirectClient.
  • agentage daemon start|stop|status - status shows pid/port/uptime/served/version and warns on a bin-vs-daemon version mismatch with a restart hint (as does agentage status).
  • Verbs never bypass the MemoryClient seam: resolveClient() = daemon by default, DirectClient on --no-daemon, AGENTAGE_NO_DAEMON=1, or when the daemon is unreachable and cannot be spawned. Fallback is silent and seamless.
  • Config dir + port honor AGENTAGE_CONFIG_DIR + AGENTAGE_DAEMON_PORT, so tests and parallel setups never collide with a real daemon.

Why

A single writer kills the git index.lock contention between concurrent clients of the same vault, and gives the upcoming local MCP surface a process to live in. The daemon and the direct fallback share the exact same engine code - the daemon is just its longest-lived caller.

Provenance

Ported from the archived v0.24 daemon in this repo's git history (pre-reboot): pidfile lifecycle, detached autostart + health poll, action dispatch, graceful stop, daemon subcommand. Stripped everything agent-runtime (agents, runs, schedules, websockets, machines, projects, settings, express) and adapted the vault actions to the memory-core router. One deliberate deviation from the harvested pattern: spawn instead of fork for the detached start - fork always opens an IPC channel that keeps the parent event loop alive even after unref(), which made the CLI hang after daemon start.

R6 guard amendment

The package guard drops the now-obsolete strip markers daemon-entry, ensure-daemon, and :4243 (the daemon is a sanctioned feature again). The bans on express/ws/native sqlite and the exact runtime dep allowlist (@agentage/memory-core + chalk + commander + open) are unchanged - zero new dependencies.

Tests

  • Unit: action dispatch (mock MemoryClient), DaemonClient round trip against an in-process server on an ephemeral port (never forks), ensureDaemon fallback matrix (mocked probe/spawn), pidfile/port lifecycle, daemon subcommands. 156 tests green, coverage 86/79/75/88.
  • E2E: new daemon tier @p0 - explicit daemon start on an OS-picked ephemeral port + isolated config dir, all 6 verbs routed through it (proven by the daemon's served counter), daemon stop terminates the process and removes pid/port files; plus a --no-daemon bypass check. Existing offline tiers keep running against the in-process engine (AGENTAGE_NO_DAEMON=1 in the harness) so they stay deterministic and never spawn background processes. Full suite 18/18 green locally.
  • Manual sanity: isolated config dir -> daemon start -> memory write/search via the CLI (daemon served counter advanced) -> daemon stop -> process gone, pidfile gone.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎉 PR Validation ✅ PASSED

Commit: f14b94cef53ed6906d08b5a165efae015511abce
Branch: feature/m2_5-daemon

Checks:

  • ✅ Release guard (no version/changelog changes)
  • ✅ Dependencies installed
  • ✅ Type check passed
  • ✅ Linting passed
  • ✅ Format check passed
  • ✅ Tests + coverage passed
  • ✅ Build successful

Ready to merge!


🔗 View workflow run
⏰ Generated at: 2026-07-06T06:36:39.966Z

@vreshch vreshch marked this pull request as ready for review July 6, 2026 06:37
@vreshch vreshch merged commit 9bc3463 into master Jul 6, 2026
2 checks passed
@vreshch vreshch deleted the feature/m2_5-daemon branch July 6, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant